﻿using System;
using System.Diagnostics;
using System.Text;
using System.Text.RegularExpressions;

namespace DotNetConverter
{
    public static class Converter
    {
        private const string AccessModifiers = "Public|Protected|Friend|Private|Protected Friend";
        private const RegexOptions comIgMult = RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Multiline;
        private const string ParamRe = @"((Optional\s+)?((?<ref>ByVal|ByRef)\s+)?(?<paramName>\w+)(\s+As\s+(?<paramType>[\w\.]+))?(\s+=\s+(?<eq>[\w\.\""]+))?((?<comma>,) )?)";
                              //(?<params>(Optional\s+)?((?<ref>ByVal|ByRef)\s+)?(?<paramName>\w+)(\s+As\s+(?<paramType>[\w\.]+))?(\s+=\s+(?<eq>[\w\.\""]+))?(, )?)
        private static readonly Regex _comments = new Regex(@"^(?<before>(""[^""]*"")*?)*[^'\r\n]*'(?<after>[^\r\n]*)", RegexOptions.Compiled | RegexOptions.Multiline);
        private static readonly Regex _option = new Regex(@"^\s*Option\s[^\r\n]+", comIgMult);
        private static readonly Regex _imports = new Regex(@"^\s*Imports\s+(?<ns>[^\r\n]+)", comIgMult);
        //private static readonly Regex _class = new Regex(String.Format(@"^\s*(?<access>({0})\s+)?Class\s+(?<name>\w+)(?:\s+Inherits\s+(?<inherits>[\w\.]+))?(?:\s+Implements\s+(?<implements>[\w\.]+)(,\s*(?<moreImplements>[\w\.]+))*)?", AccessModifiers), comIgMult);
        private static readonly Regex _classBegin = new Regex(String.Format(@"^\s*(?<access>({0})\s+)?Class\s+(?<name>\w+)(?:\s+Inherits\s+(?<inherits>[\w\.]+))?(?:\s+Implements\s+(?<implements>[\w\.]+)(,\s*(?<moreImplements>[\w\.]+))*)?\s*$", AccessModifiers), RegexOptions.Compiled | RegexOptions.Singleline);
        private static readonly Regex _classEnd = new Regex(@"\s*End\s+Class");
        private static readonly Regex _vars = new Regex(String.Format(@"(?<access>{0}|Dim)\s(?!(?:Class|Sub|Function))(?<name>\w+)(,\s+(?<moreNames>\w+))*(\s+As\s+(?<type>[\w\.]+))?(\s*=\s*(?<new>New\s)?(?<eq>[\w\.\""]+))?", AccessModifiers), comIgMult);
        private static readonly Regex _instantiation = new Regex(@"(?<name>\w+)\s*=\s*New\s+(?<new>[\w\.]+)");
        private static readonly Regex _params = new Regex(String.Format(@"(?<access>{0}|Dim)\s(?!(?:Class|Sub|Function))(?<name>\w+)(,\s+(?<moreNames>\w+))*(\s+As\s+(?<type>[\w\.]+))?(\s*=\s*(?<new>New\s)?(?<eq>[\w\.\""]+))?", AccessModifiers), comIgMult);
        private static string methodsRe = String.Format(@"((?<access>{0})\s+)?(?<funcType>Sub|Function)\s+(?<name>\w+)\((?<params>(Optional\s+)?((?<ref>ByVal|ByRef)\s+)?(?<paramName>\w+)(\s+As\s+(?<paramType>[\w\.]+))?(\s+=\s+(?<eq>[\w\.\""]+))?(, )?)*\)(\s+As\s+(?<returnType>[\w\.]+))?(\s+Handles\s+[\w\.]+)?(?<body>.+?)^\s*End\s+\k<funcType>", AccessModifiers, ParamRe);
        private static readonly Regex _methodBegin = new Regex(String.Format(@"((?<access>{0})\s+)?(?<funcType>Sub|Function)\s+(?<name>\w+)\((?<params>{1}*)\)(\s+As\s+(?<returnType>[\w\.]+))?(\s+Handles\s+[\w\.]+)?", AccessModifiers, ParamRe), RegexOptions.IgnoreCase);
        private static readonly Regex _exitMethod = new Regex(@"^\s*Exit (Sub|Function)");
        private static readonly Regex _methodEnd = new Regex(@"\s*End\s+(Sub|Function)", RegexOptions.IgnoreCase);
        private static readonly Regex _methods = new Regex(methodsRe, comIgMult);
        private static readonly Regex _with = new Regex(@"\bWith New (?<new>[\w\.]+)");
        private static readonly Regex _endWith = new Regex(@"\bEnd With\b");
        private static readonly Regex _if = new Regex(@"If\s+(?<not>Not\s*)?(?<condition>(.(?!Then))+).Then(\s+(?<then>(.(?!Else|\r))+))?(\s+Else\s+(?<else>(.(?!\r))+))?", RegexOptions.IgnoreCase);
        private static readonly Regex _else = new Regex(@"^\s*Else[^\r]*", RegexOptions.IgnoreCase | RegexOptions.Compiled); 
        private static readonly Regex _ifEnd = new Regex(@"^\s*End If[^\r]*", RegexOptions.IgnoreCase | RegexOptions.Compiled);
        //For Each fld In r.Fields ... ... Next fld
        private static readonly Regex _foreachBegin = new Regex(@"^\s*For Each (?<name>\w+) In (?<in>[\w\.]+)", RegexOptions.IgnoreCase);
        private static readonly Regex _forEachEnd = new Regex(@"^\s*Next \w+");
        private static readonly Regex _generalLine = new Regex(@"", RegexOptions.IgnoreCase);

        public static string VBtoCSharp(string VB)
        {
            StringBuilder cSharp = new StringBuilder();
            VB = Regex.Replace(VB, @"[ \t]+_\r\n[ \t]*", " ");
            string[] lines = VB.Split(new string[]{"\r\n"}, StringSplitOptions.None);
            Regex closer = null;
            bool inMethod = false;
            string with = null;
            string returnVal = null;

            for(int i = 0; i < lines.Length; i++)
            {
                string line = lines[i].Trim();

                if (_comments.IsMatch(line))
                    line = _comments.Replace(line, CommentsEval);

                if(closer != null && closer.IsMatch(line))
                {
                    closer = null;
                }

                if (with != null)
                {
                    line = Regex.Replace(line, String.Format(@"^\s*\.{0}", Regex.Escape(with)), "with");
                }

                if(Regex.IsMatch(line, @"^\s*$"))
                {
                    cSharp.AppendLine(line);
                }
                else if (_option.IsMatch(line))
                {
                    cSharp.AppendLine(_option.Replace(line, ""));
                }
                else if (_imports.IsMatch(line))
                {
                    cSharp.AppendLine(_imports.Replace(line, ImportsEval));
                }
                else if(Regex.IsMatch(line, @"^\s*On Error", RegexOptions.IgnoreCase))
                {
                }
                else if(_vars.IsMatch(line))
                {
                    cSharp.AppendLine(_vars.Replace(line, VarsEval));
                }
                else if(inMethod && _instantiation.IsMatch(line))
                {
                    cSharp.AppendLine(_instantiation.Replace(line, InstantiationEval));
                }
                else if (_classBegin.IsMatch(line))
                {
                    string classBegin = line;

                    while (_classBegin.IsMatch(classBegin + lines[i+1]))
                    {
                        classBegin += lines[++i];
                    }

                    //Match classMatch = _classBegin.Match(classBegin);
                    cSharp.AppendLine(_classBegin.Replace(classBegin, ClassEval));
                }
                else if (_methodBegin.IsMatch(line))
                {
                    inMethod = true;
                    cSharp.AppendLine(_methodBegin.Replace(line, MethodBeginEval));

                    if (_methodBegin.Match(line).Groups["funcType"].Value.ToLower() == "function")
                    { 

                    }
                }
                else if(_exitMethod.IsMatch(line))
                {
                    cSharp.AppendFormat("return{0};", returnVal == null ? "" : (" " + returnVal));
                }
                else if (_methodEnd.IsMatch(line) || _ifEnd.IsMatch(line) || _classEnd.IsMatch(line) || _forEachEnd.IsMatch(line))
                {
                    inMethod = false;
                    cSharp.AppendLine("}");
                }
                else if (_if.IsMatch(line))
                {
                    cSharp.AppendLine(_if.Replace(line, IfEval));
                }
                else if (_else.IsMatch(line))
                {
                    cSharp.AppendLine("}else{");
                }
                else if (_foreachBegin.IsMatch(line))
                {
                    cSharp.AppendLine(_foreachBegin.Replace(line, ForEachEval));
                }
                else
                {
                    line = Regex.Replace(line, @"(?<=(\s|\())(?<bool>True|False)(?=(,|\)|$))", BoolEval);
                    cSharp.Append(line);
                    cSharp.AppendLine(";");
                }
            }

            return cSharp.ToString();
        }

        private static string SearchFor(string KeyWord)
        {
            switch (KeyWord.ToLower())
            { 
                case "option":

                    return @"\w+\s+\w+";

                case "imports":

                    return @"[\w\.]+";

                case "dim":
                case "friend":
                case "public":
                case "private":

                    return @"\w+";

                case "protected":

                    return @"(\s+(?<friend>friend)?)\w+";

                default :

                    throw new NotImplementedException(KeyWord);
            }
        }

        private static string ImportsEval(Match M)
        {
            return String.Format("using {0};", M.Groups["ns"].Value);
        }

        private static string CommentsEval(Match M)
        {
            return String.Format("{0}//{1}", M.Groups["before"].Value, M.Groups["after"].Value);
        }

        private static string MethodBeginEval(Match M)
        {
            string type = VBtoCSharpType(M.Groups["returnType"].Value);

            string returnType =
                M.Groups["funcType"].Value.ToLower() == "sub" ?
                    "void" :
                    type;

            //bool first = true;

            string @params = M.Groups["params"].Value;// Regex.Replace(M.Groups["params"].Value, @"[,\s]+$", "");
            @params = Regex.Replace(@params, ParamRe, ParamEval);

            string res = String.Format("{0} {1} {2}({3}){{", VBtoCSharpAccessModifier(M.Groups["access"].Value), returnType, M.Groups["name"], @params);
            return res;
        }

        private static string ParamEval(Match M)
        { 
            //ref,paramName,paramType,eq
            string @ref = M.Groups["ref"].Value.ToLower() == "byref" ? "ref " : "";

            return String.Format("{0}{1} {2}{3}", @ref, VBtoCSharpType(M.Groups["paramType"].Value), M.Groups["paramName"].Value, M.Groups["comma"].Value);
        }

        private static string MethodsEval(Match M)
        {
            string type = VBtoCSharpType(M.Groups["returnType"].Value);
            string body = M.Groups["body"].Value;
            string @params = "";

            string returnType = 
                M.Groups["funcType"].Value.ToLower() == "sub" ?
                    "void" :
                    type;

            if (M.Groups["funcType"].Value.ToLower() == "function")
            {
                body = type + " __functionReturnObject;\r\n" + body;
                body = Regex.Replace(body, String.Format(@"^\s*{0}\s+=", M.Groups["name"]), "__functionReturnObject =");
                body = Regex.Replace(body, @"\bExit Function\b", "return __functionReturnObject;");
            }
            else
            {
                body = Regex.Replace(body, @"\bExit Sub\b", "return;");
            }

            bool first = true;

            MatchCollection paramMatches = Regex.Matches(M.Groups["params"].Value, ParamRe);

            foreach (Match par in paramMatches)
            {
                @params += par;

                if (!first)
                    @params += ",";
                first = false;
            }

            string res = String.Format("{0} {1} {2}({3}){{{4}}}", M.Groups["access"], returnType, M.Groups["name"], @params, body);
            return res;
        }

        private static string InstantiationEval(Match M)
        {
            return String.Format("{1} {0} = new {1}();", M.Groups["name"].Value, M.Groups["new"].Value);
        }

        private static string VarsEval(Match M)
        {
            string names = M.Groups["name"].Value;
            string type = "object";
            string instance = "";
            string access = M.Groups["access"].Success ? VBtoCSharpAccessModifier(M.Groups["access"].Value) : "";

            foreach(Capture otherName in M.Groups["moreNames"].Captures)
                names += "," + otherName.Value;

            if (M.Groups["type"].Success)
                type = VBtoCSharpType(M.Groups["type"].Value);

            if(M.Groups["eq"].Success)
            {
                instance = " = ";

                if (M.Groups["new"].Success)
                    instance += "new ";

                instance += M.Groups["eq"].Value;

                if (M.Groups["new"].Success && !Regex.IsMatch(instance.Trim(), @"\)$"))
                    instance += "()";
                    
            }
            string res = String.Format("{0} {1} {2} {3};", access, type.Trim(), names.Trim(), instance);
            return res;
        }

        private static string GeneralEval(Match M)
        {
            string result = M.Value;

            //result = Regex.Replace(

            return result + ";";
        }
        
        private static string ForEachEval(Match M)
        {
            return String.Format("foreach({2}{0} in {1})\r\n{{", M.Groups["name"].Value, M.Groups["in"].Value, "object ");
        }

        private static string BoolEval(Match M)
        {
            return M.Value.ToLower();
        }

        private static string IfEval(Match M)
        {
            StringBuilder @if = new StringBuilder("if");
            string not = M.Groups["not"].Success ? "!" : "";
            string condition = M.Groups["condition"].Value;

            condition = condition.Replace("=", "==");// this is obviously not good enough as there may be a valid signle equals sign
            condition = condition.Replace("<>", "!=");

            @if.AppendFormat("({0}{1})\r\n{{", not, condition);
            
            if(M.Groups["then"].Success)
                @if.AppendFormat("{0};}}", M.Groups["then"].Value);

            if(M.Groups["else"].Success)
                @if.AppendFormat("else{{{0};}}", M.Groups["else"].Value);

            return @if.ToString();
        }

        private static string ClassEval(Match M)
        {
            Group accessGroup = M.Groups["access"];
            Group inheritsGroup = M.Groups["inherits"];
            Group implementsGroup = M.Groups["implements"];

            string access = "";
            string inheritance = "";

            if (accessGroup.Success)
                access = VBtoCSharpAccessModifier(accessGroup.Value) + " ";

            if (inheritsGroup.Success || implementsGroup.Success)
                inheritance = ":";

            if (inheritsGroup.Success)
            {
                inheritance += inheritsGroup.Value;
                if (implementsGroup.Success)
                    inheritance += ",";
            }

            if (implementsGroup.Success)
                inheritance += implementsGroup.Value;

            foreach (Capture moreImplements in M.Groups["moreImplements"].Captures)
                inheritance += "," + moreImplements.Value;

            return String.Format("{0}class {1}{2}\r\n{{", access, M.Groups["name"].Value, inheritance);
        }

        private static string General(string Code)
        {
            return Code;
        }

        private static string VBtoCSharpType(string Type)
        {
            switch (Type.ToLower())
            { 
                case "object":
                case "string":
                case "short":
                    return Type.ToLower();

                case "integer":

                    return "int";

                case "boolean":

                    return "bool";
            }

            return Type;
        }

        private static string VBtoCSharpAccessModifier(string Modifier)
        {
            Modifier = Modifier.Trim().ToLower();

            switch (Modifier)
            { 
                case "public":
                case "protected":
                case "private":

                    return Modifier;
                
                case "friend":

                    return "internal";

                case "protected friend":

                    return "protected internal";

                case "dim":

                    return "";
            }

            throw new NotSupportedException(String.Format("the access modifier '{0}' is not supported", Modifier));
        }
    }
}
